logo
menu

패키지 매니저 알아보기 | npm, yarn, pnpm 그리고 corepack 까지

2023. 11. 24.

  • #개발환경

패키지 매니저의 발전

  1. npm
  1. yarn classic
  1. yarn berry
  1. pnpm
 

사전 지식

Phantom Dependency

  • 💡 npm 과 yarn classic 모두 node_modules 가 flat 한 형태의 구조이기 때문에 내가 설치하지 않은 의존성을 사용할 수 있음
    • ⇒ 이런 현상으로 많은 위험이 있을 수 있음
      notion image
    • 왜냐하면 A 패키지를 설치했는데 A 패키지의 의존성 패키지가 B 가 있는 경우 B 도 node_modules 바로 밑에 (A 과 동일한 레벨 위치) 평평한 구조로 생기기 때문 (v3 이미지)
      • ex) App 에서 A 패키지 설치했는데 B 도 사용할 수 있음
  • 팬텀 디펜더시 문제 사례
    • notion image
      1. a 패키지에 axios 설치
      1. a 에서 설치한 axios 는 root 의 node_modules 에 설치됨
        1. npm install -w
      1. b 에서 axios 를 따로 설치하지 않고 axios 사용
        1. 이때 b 에서 axios 패키지에 접근이 가능함
          왜냐하면
        2. require(”axios”) 로 axios 패키지 가져옴
        3. package-b 에 axios 가 없음 → 상위로 이동
        4. root 에서 axios 찾음 → axios 있음! → 그래서 접근 가능
      1. a 에서 axios 사용하지 않는다고 판다해서 axios 제거
      1. b 에서 axios 사용하는 구문에서 터짐
        1. ⇒ 즉 b 는 a 의 변경 사항으로 인해 영향을 받음! 안티패턴!!!

node_modules 의 패키지에 접근하는 구조

  • npm 와 같은 패키지 매니저에서
    • package.json 에서 프로젝트 관련 정보를 관리하고
    • node_modules 폴더 안에 사용 가능한 패키지가 존재
    • 소스코드에서 require 또는 import 로 패키지에 접근하면
      • 상대 경로인 경우 해당 파일의 위치를 찾고
      • 그냥 이름만 적혀 있는 경우, 현재 위치의 node_modules 를 찾아보고 없으면 상위 폴더로 이동하면서 … 찾음!
require, import 와 같이 코드로 패키지에 접근하면 해당 위치에서 node_moduels 찾고 없으면 상위 폴더에서 node_moduels 찾고 없으면 …. 계속 반복해서 해당 패키지를 찾을려고 함
 

corepack

  • corepack 은 최근에 npm(NodeJS) 에서 추가된 기능으로 nodejs 설치하면 corepack 도 같이 설치됨
  • corepack 은 yarn 개발자가 추가한 기능으로 package.json 에서 어떤 패캐지 매니저의 버전을 명시해서 관리하는 것으로 이로 인해 해당 프로젝트는 해당 패키지 매니저의 버전을 사용할 수 있게 자동으로 해주는 기능!
## 프로젝트 초기화후 corepack 활성화 필요 mkdir root-project cd root-project corepack enable ## -2 는 yarn-berry 버전으로 초기화 yarn init -2 -w ## 이미 있는 경우? # yarn set version berry
{ "name": "root-project", "packageManager": "yarn@4.0.1", "workspaces": [ "packages/*" ] }
  • 해당 프로젝트는 yarn 의 4 버전을 사용한다는 의미
 
 

npm

notion image
  • NodeJS 를 설치하면 기본 설치된 npm
  • npm v2 는
    • 중첩된 구조로 의존성 관리 하지만, 해당 방식으로 인해 이슈 발생
      • windows 의 경우 폴더의 길이가 제한이 있어 중첩되서 많이 들어가면 폴더명이 겹치는 에러 발생
      • A 사람이 패키지를 설치하고 (같은 환경에서) B 도 똑같이 설치했는데 잘 설치되지 않는 이슈 발생
        • ⇒ 그래서 lock 파일 도입함 (첫 시작은 yarn 에서 시작)
  • npm v3 은
    • node_modules에서 flat 한 구조로 관리

yarn classic

  • yarn 의 첫번째 방식으로 npm 의 동작 방식을 그대로 가져오면서 아쉬운 부분들을 해결하고자 Facebook, Google 이 공동 개발
    • Lock 파일 개념 도입해서 일관적으로 의존성을 재설치할 수 있도록 함
      • ⇒ 앞서 npm v2 의 문제점이 같은 환경에서 A, B 가 각각 설치했는데 잘 설치되지 않는 이슈 보완
    • 보안 향상
    • 병렬 설치로 성능 향상
    • 워크스페이스 개념 도입
      • 후에 npm 도 워크스페이스 개념 도입
 

pnpm

  • 기존 패키지 매니저 (npm, yarn) 을 개선하고자 어떤 개발자가 pnpm 출시
  • ✨ 하드링크와 소프트 링크를 적절히 사용해서 성능 향상과 디스크 효율성을 강조
  • npm v3 의 flat 한 구조를 버리고 중첩구조를 유지하면서 문제를 해결
    • flat 한 구조는 팬텀 디펜더시 문제를 해결
    • 중첩구조를 유지하며서 문제 해결은 일관성 있는 설치(lock 파일) 및 속도 개선 등
  • 워크스페이스 기능 제공
 

yarn berry

  • yarn classic 과 다른 완전 새로운 버전 (2020 출시)
  • node_modules 를 사용하지 않고 압축파일을 사용하는 (plug in play - pnp) 방식 사용
    • 압축파일을 사용해서 설치 시간을 최소화하고 설치하지 않아도 사용 가능한 개념(zero-install) 도입
      • 하지만 완전 zero-install 은 아님
  • (node_modules 방식이 아니기 때문에) 엄연한 의존 관계로 팬텀 디펜더시와 같은 문제가 발생하지 않음
  • 워크스페이스 제공
 

패키지 매니저와 workspace

패키지를 가져오는 방식

  1. 외부에서 가져오는 방식 npm i ...
    1. notion image
  1. 내부에서 가져오는 방식 npm link ...
    1. notion image
 

npm link

notion image
위와 같은 구조를 구성한다고 하면
# root mkdir project-root cd project-root npm init -y mkdir package-a cd package-a npm link ./package-a cd .. mkdir package-b cd package-b npm link ./package-b
참고
notion image
notion image
  1. root 의 main.js 실행
  1. require 만나서 현재 파일의 위치의 node_modules 에서 package-b 폴더 가 있는지 찾음
  1. 있으니깐 node_moduels/package-b 안에 있는 package.json 파일을 찾음
  1. node_moduels/package-b 의 package.json 파일에서 main 을 찾아서 정의된 파일 위치(index.js) 을 가져옴
 

npm-workspace

# root mkdir project-root cd project-root npm init -y ## 워크스페이스 추가 ## -w 는 워크스페이스 의미 npm init -y -w ./packages/a npm init -y -w ./packages/b
 
# root project-root 의 package.json { "name": "project-root", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "workspaces": [ "packages/a", "packages/b" ] }
 
추가 명령어
## 워크스페이스 생성 npm init -y -w ./packages/a # --workspace 워크스페이스를 의미 ➜ npm start --workspace a # --workspace 의 단축형 -w ➜ npm start -w a # --worksacpes 여러 워크스페이들을 의미 ➜ npm start --workspaces # --workspaces 의 단축형 -ws ➜ npm start -ws # --if-present 는 해당 스크립트가 있는 경우 수행 ➜ npm start -ws --if-present # --include-workspace-root 는 root 의 스크립트 ➜ npm start -ws --include-workspace-root # --if-present --include-workspace-root root 가 있을때만 수행 ➜ npm start -ws --if-present --include-workspace-root # 직접 의존하고 있는 관계들만 표현 ➜ npm ls
 

yarn-classic-workspace

  • yarn 1.0 부터 사용 가능
    • yarn 1버전대는 private 속성을 true 로 해야함
  • yarn link 제공 (npm 과 동일)
  • package.json 의 workspaces 속성을 통해 정의 (npm 과 동일)
  • 프로젝트 내의 모든 패키지의 의존성이 함께 설치되고(lock 파일) 관리되어 충돌이 적고 최적화에 유리
 
# root mkdir project-root cd project-root yarn init -y -p # private 모드로 init ## 하위 패키지 생성 mkdir packages packages/a packages/b cd package/a && yarn init -y cd .. # 루트로 다시 이동 cd package/b && yarn init -y
{ "name": "project-root", "version": "1.0.0", "main": "index.js", "license": "MIT", "private": true, ## 추가 "workspaces": [ "packages/*" ] }
# 실행 yarn # workspace 에 외부 의존성 추가 yarn workspace a add axios # workspace 스크립트 수행 yarn workspace a start # 여러 workspace 스크립트 수행 yarn workspaces run start # workspace 의존 관계 yarn workspaces info
 

yarn-berry-workspace

  • yarn 의 2번째 버전
  • 💡 기본적으로 명시적인 의존 관계를 나타내야 사용 가능 (yarn-classic 과 차이점)
    • npm 과 yarn-classic 은 팬텀 디펜더시 문제로 각 패키지들이 root 의 node_modules 에 flat 한 형태로 있기 때문에 모든 패키지에서 사용 가능한 문제가 있음
      • 하지만, yarn-berry 는 명시적으로 의존 관계를 나타내야 사용할 수 있음
  • 💡 node_modules 에 패키지를 저장하는 방식이 아닌 패키지를 압축해서 한 개의 파일을 .yarn/cache 폴더에 수평적으로 저장
    • ⇒ Plug’n’Play (PnP) 방식
    • 수평적으로 존재하기 때문에 빠르게 찾을 수 있음
    • 압축파일을 설치하기 때문에 파일 개수가 감소하여 설치가 빠름
      • Zero Install 을 이용하여 저장소에서 함께 관리 (압축 파일을 git에 함께 올려서 관리)
    • 팬텀 디펜더시가 발생하지 않음
      • 내가 직접 설치하지 않은 패키지를 사용 가능한 것!
  • 단점
    • Zero Install 이라고 해서 아무것도 설치하지는 않음
    • PnP 방식으로 압축파일을 관리하기 때문에 저장소 자체가 커질 수 있음
    • 패캐지들을 압축 파일로 관리하기 때문에 사용하기 위한 IDE 추가 설정 필요
      • IDE 에서 직접 사용하는 많은 도구(ESLint, Typescript, Prettier 등) 들을 SDK 를 통해 우회 호출할 수 있도록 추가 설정 필요
        # install 후 IDE 에 인식되게하려면 아래 명령어 수행 yarn dlx @yarnpkg/sdks vscode
yarn-classic 과 의존성 추가하는 것은 같음
 

pnpm

notion image
  • 빠르고, 효율적인 패키지 매니저
  • 모노레포 지원하고 , flat 하지 않은 node_modules 방식으로 엄격한 의존관리가 가능
    • → 엄격한 의존 관리 == 팬텀 디펜던시 문제 해결
  • 시스템 내에 단일 패키지 스토어에 모든 의존성을 보관하기 때문에 디스크 공간이 절약됨
  • 필요한 의존성을 식별해서 스토어로 가져오고 (기존에 설치되지 않은 것만 설치), 디렉터리 구조를 계산해서 하드 링크하는 과정을 가지기 때문에 설치 속도가 빠름
    • ⇒ 필요한 패키지들을 스토어에서 관리하고 각각의 프로젝트 에서 필요한 패키지를 설치하면 store 에서 하드 링크로 가져오는 방식이 때문에 설치 속도가 빠름
      🧐 최초 설치시에는 store 에 저장후 하드링크하지만, store 에 있는 경우는 하드링크이기 때문에 빠르다는거 같음
      🧐  하드링크로 해서 첫번째만 외부에서 다운받고 그 후 부터 스토어에 있는 거면 외부에서 다운이 아닌 복/붙 느낌이라서 빠르다고 하는건가??
  • pnpm 은 symlink 를 사용해서 프로젝트의 직접적인 의존성만을 모듈 디렉터리의 루트로 추가함
    • notion image
  • 각 패키지에서 사용하는 의존성들은 패키지 내부에서 관리를 하고 (공통적인 모듈로 올리지 않음) 각 패키지의 모듈들을 symlink 로 링크되어 관리함.
    • ⇒ 💡 이런 구조로 관리하기 때문에 팬텀 디펜더시 문제를 해결했고, 엄격하고 직접적인 의존성 구조를 가지고 있음
 
# root mkdir project-root cd project-root pnpm init corepack use pnpm@8.10.0 # corepack 설정 ## 하위 패키지 생성 mkdir packages packages/a packages/b cd package/a && pnpm init cd .. # 루트로 다시 이동 cd package/b && pnpm init
# pnpm-workspace.yaml packages: - "packages/*"
  • pnpm 의 경우 workspace 를 package.json 의 workspace 속성이 아닌
    • pnpm-workspace.yaml 파일로 관리!
       
의존관계 추가
{ "name": "a", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "axios": "^1.6.0", "b": "workspace:*" } }
 
명령어 참고
# 특정 패키지에 의존성 설치 pnpm --filter a add axios # 특정 패키지 의존성 제거 pnpm --filter a remove axios # 스크립트 실행 pnpm --filter a start ## -r 옵션은 run 의 단축 pnpm -r run start # root 스크립트 수행? pnpm -r --include-workspace-root start # 의존 관계 pnpm -r list # pnpm store 의 경로를 변경하는 명령어 # pnpm config get store-dir pnpm config set store-dir ~/.pnpm-store